/*
The surface-physics project: http://code.google.com/p/surface-physics/
Adds real world physics based on the Bullet physics engine, to bases in the Orbiter space
flight simulation software.

Written by Abhijit Nandy 
License : Apache License 2.0

Bump_MeshCollider.h

+ Reads the physics definition of a passed vessel class from a physics file of the
  same name.
+ Created and destroyed on demand so must support raycast vehicle removal in-sim
+ Must support orbiter vessel forces and apply them via Bullet
+ Must support vessel interception and attaching
+ Must support vessel on vessel (landing DG on flight deck eg.)
+ Also vessel must be released to Orbiter at Bullet's state.
+ Requests deletion from base when controlled vessel is far from base
+ Collisions in space will also be supported through this class
+ More detailed DG specific class may replace this later
+ This class is the culmination for phase 1 of this project(it supports almost everything developed so far)
-------
Completed
A drawback is that the vessel must be intercepted as a kinematic object above the maximum terrain
height.

However, the kinematic object is not converted to a dynamic vehicle till a collision occurs, which is
good.


*/


#pragma once


#include "BulletBaseVehicle.h"
#include "../Terrain/Bump_TerrainPatchOru.h"

#include "../BulletBase/Bump_MshCollisionShapeAttached.h"


//Orulex types
typedef union{
	double data[4];
	struct {double x,y,z,m;};
}VECTOR4;

extern double ORUGetElevByVec(VECTOR4 pos);
extern VECTOR4 v32v4(VECTOR3 v);


enum ColliderStateType{
	CS_UNINITIALIZED,
	CS_ORBITER_REGIME,
	CS_PRE_BULLET_REGIME,
	CS_BULLET_REGIME,
	CS_TIMEWARP,
	CS_FROZEN
};


/**
 * The Bump_Attachment to create and manage a Vessel Collider
 */
class Bump_MeshCollider : Bump_BaseObject
{

	static Registrar registrar;

	//Position wrt base, updated every step
	VECTOR3 gposVesselPos, gposElevVesselPos, rposVesselPos, gposRefBody;
	double lng, lat, rad;

	double cogElev, waitCount;

	//Mesh handles, light handles, animation handles, other Orbiter primitives etc here

	//The attached VESSEL's pointer
	VESSEL3 *vCollider;


	//Collider specific stuff
	ColliderStateType colliderState;
	CollisionInfo collisionInfo;
	btVector3 aabbMin, aabbMax; //current AABB of colliding object
	int collisionCount, MAX_COLLISIONS;
	bool kinematicBodyActive, dynamicVehicleActive, collisionMeshActive, hoverEnginesFiredForFlight;

	//Raycast vehicle specific
	btRigidBody 		*rbCollider, *rbChassis;
	btVehicleRaycaster  *vehicleRayCaster;
	btRaycastVehicle	*vehicle;
	btRaycastVehicle::btVehicleTuning	wheelTuning; //used to tune wheels, do not remove
	btCollisionShape	*chassisBoxShape, *frontWheelShape, *backWheelShape, *basicCollisionShape;
	btCompoundShape 	*compoundChassisShape;
	btScalar 			mass, vesselRadius;

	//This may be redundant
	btVector3 trueChassisPos;

	//Rendering OpenGL object
	GL_ShapeDrawer* m_ShapeDrawer;

	//The position of this object in the PhysicsEngine rendering objects list
	//Useful when de-registering this object
	RenderObjectList::iterator renderFuncPos;

	int transferCount;


	/**
	 * Vehicle and wheel properties, these are wrapped inside the class rather
	 * than keeping them global to prevent interference with any other vehicle objects
	 */

	//Engine parameters
	float	maxEngineForce;//this should be engine/velocity dependent
	float	maxBrakingForce;
	float	gBrakingForce;
	float	gVehicleSteering;
	float	gEngineForce;
	float 	reverseEngineForce;

	//Transmission : simple gear model which varies engine force only
	int 	maxGear; //Reverse and neutral indicated by 'R' & 'N'
	int 	minGear;
	int 	m_gear;

	//Steering parameters
	float	steeringIncrement;
	float	steeringClamp;

	//Current vessel transform
	btVector3 linearVel, angularVel;

	//Wheel parameters
	float	frontWheelRadius, backWheelRadius;
	float	wheelHalfWidth;	 //actual width is 2.364(twice)
	float	wheelFriction;
	float	wheelFwdBackHalfDist;
	float   wheelLeftRightHalfDist;
	float	connectionHeight;
	int 	rightIndex;
	int 	upIndex;
	int 	forwardIndex;
	btVector3 wheelDirectionCS0;
	btVector3 wheelAxleCS;

	//Suspension parameters
	float	suspensionStiffness;
	float	suspensionDamping;
	float	suspensionCompression;
	float	rollInfluence;//1.0f;
	float 	suspensionRestLength;

	//Heights and initial location
	float 	initialVehicleHeight; //Height from which vehicle is dropped on terrain at physics start
	float 	chassisHeight; //Height of chassis above vehicle origin
	float 	chassisHalfLength;
	float 	chassisHalfWidth;
	float 	chassisHalfHeight;

	btTransform transLocalChassis;


	//Supporting terrain
	Bump_TerrainPatch *ground;

	// Bump Collision meshes
	Bump_MshCollisionShapeAttached collMshChassis;
	Bump_MshCollisionShapeAttached collMshWheel[4];



	//Utility stuff
	int 	readPhysicsFile(); //not used here
	int		transformVessel();
	int		createBulletRigidBodyVehicle(bool isKinematic);
	int		destroyBulletRigidBodyVehicle();
	int		processCommand();
	bool	vesselInFlight(bool forceFlight);



public:

	static Bump_BaseObject * create() { return new Bump_MeshCollider; }



	Bump_MeshCollider();
	virtual ~Bump_MeshCollider();

	//Main control functions
	int		clbkSetClassCaps(void);
	int		clbkPostStep(double SimT, double SimDT, double MJD);
	int		clbkPhysicsRender(void);



	/**
	 * Get elevation above terrain, inlined for speed
	 * gposVesselPos MUST BE CORRECT before this function is called
	 * gposVesselPos should NOT be got in this function
	 * offset needed as tdps call this function differently
	 * The global position is calculated from Physics(converted to global frame)
	 */
	void
	getPositionAboveTerrain(VECTOR3& gposElevVesselPos, VECTOR3& gposVesselPos, double offset)
	{
		VECTOR3 gdirVesselRadiusVector;
		double terrainRadius;

		//Get position of reference body
		oapiGetGlobalPos(bumpBase->hObjRefBody, &gposRefBody);

		//Get Equ position too, needed to bypass Orulex if vessel above Orulex terrain
		vCollider->GetEquPos(lng, lat, rad);


		switch(bumpBase->terrainGenerator){
		case TT_PLANETCURVE:
			gposElevVesselPos = gposVesselPos;
			break;
		case TT_ORULEX:
			gdirVesselRadiusVector = gposVesselPos - gposRefBody;
			terrainRadius = ORUGetElevByVec(v32v4(gdirVesselRadiusVector));

			//Check if height of vehicle above the terrain.
			//This function may be called when vehicle high above terrain

			oapiWriteLogV("rad: %f, terrainRadius:%f", rad, terrainRadius);

			if(rad > terrainRadius){
				gposElevVesselPos = gposVesselPos;
			}
			else{
				//We can safely position vessel just on Orulex terrain
				//TODO: offset added to prevent vehicle from embedding into terrain, may need adjustments
				gposElevVesselPos = unit(gdirVesselRadiusVector) * (terrainRadius + offset);
				gposElevVesselPos += gposRefBody;		//translate position vector above RefBody
			}
			break;
		case TT_D3D11:
			break;
		default:
			oapiWriteLogV("Bump_MeshCollider::getPositionAboveTerrain: ERROR : There is no terrain generator specified for %s !!", strObjectName);
			break;
		}

	}

	/**
	 * Get distance above terrain
	 * gposVesselPos MUST BE CORRECT before this function is called
	 * gposVesselPos should NOT be got in this function
	 */
	double
	getDistanceAboveTerrain(double vesselRad)
	{
		VECTOR3 gdirVesselRadiusVector;
		double terrainRadius;

		//Get position of reference body
		oapiGetGlobalPos(bumpBase->hObjRefBody, &gposRefBody);

	
		switch(bumpBase->terrainGenerator){

		case TT_PLANETCURVE:
			return (vesselRad - bumpBase->radRefBody);

		case TT_ORULEX:
			gdirVesselRadiusVector = gposVesselPos - gposRefBody;
			terrainRadius = ORUGetElevByVec(v32v4(gdirVesselRadiusVector));
			return (vesselRad - terrainRadius);

		case TT_D3D11:
			break;

		default:
			oapiWriteLogV("Bump_MeshCollider::getDistanceAboveTerrain: ERROR : There is no terrain generator specified for %s !!",
					strObjectName);
			break;
		}

		return 0;

	}


	/**
	 * Converts a direction in vessel frame to base frame without unitizing it
	 */
	void
	convertVesselVec2BaseVec(VECTOR3& vdir, VECTOR3& rdir){
		VECTOR3 gdir, gposBB;
		vCollider->GlobalRot(vdir, gdir);			//convert vessel dir to global frame

		bumpBase->bb->GetGlobalPos(gposBB);
		gdir += gposBB;  							//converting direction, add pos of base frame in global frame
		bumpBase->bb->Global2Local(gdir, rdir);		//convert dir in global frame to base frame
		//rdir = unit(rdir); //this is in base frame
	}


	/**
	 * Converts a direction in vessel frame to base frame & unitize it
	 */
	void
	convertVesselUnitVec2BaseUnitVec(VECTOR3& vdir, VECTOR3& rdir){
		VECTOR3 gdir, gposBB;
		vCollider->GlobalRot(vdir, gdir);			//convert vessel dir to global frame

		bumpBase->bb->GetGlobalPos(gposBB);
		gdir += gposBB;  							//converting direction, add pos of base frame in global frame
		bumpBase->bb->Global2Local(gdir, rdir);		//convert dir in global frame to base frame
		rdir = unit(rdir); //this is in base frame
	}


	/**
	 * Get vessel transformation in base frame,
	 * This converts the Orbiter position into a Bullet position
	 */
	void
	getVesselTransformInBaseFrame(btTransform& transVessel)
	{

		VECTOR3 rdirX, rdirY, rdirZ;
		transVessel.setIdentity();

		//Set origin
		vCollider->GetGlobalPos(gposVesselPos);
		getPositionAboveTerrain(gposElevVesselPos, gposVesselPos, 0.);
		bumpBase->bb->Global2Local(gposElevVesselPos , rposVesselPos); //convert point, no need to add BB
		transVessel.setOrigin(btVector3((btScalar)rposVesselPos.x,
									    (btScalar)rposVesselPos.y,
										(btScalar)rposVesselPos.z) +
										bumpBase->simBoxOffset /*+
									  btVector3(0., initialVehicleHeight, 0.)*/); //may need initialHeight later


		//Set orientation
		convertVesselUnitVec2BaseUnitVec(_V(1.f, 0.f, 0.f), rdirX);
		convertVesselUnitVec2BaseUnitVec(_V(0.f, 1.f, 0.f), rdirY);
		convertVesselUnitVec2BaseUnitVec(_V(0.f, 0.f, 1.f), rdirZ);

		//Orbiter -> Bullet (just transpose - DirectX to OpenGL)
		btMatrix3x3 mBt((btScalar)rdirX.x, (btScalar)rdirY.x, (btScalar)rdirZ.x,
				 	 	 (btScalar)rdirX.y, (btScalar)rdirY.y, (btScalar)rdirZ.y,
						 (btScalar)rdirX.z, (btScalar)rdirY.z, (btScalar)rdirZ.z
				        );


		transVessel.setBasis(mBt);
	}


};
